package com.ywqf_api.common.pay.wechat;

import com.ywqf_api.entity.WeChat;
import com.ywqf_api.entity.WechatOrderResult;
import com.ywqf_api.util.MD5Util;
import com.ywqf_api.util.UUIDUtil;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.*;

public class WechatPayUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(WechatPayUtil.class);

    private static final String PROPERTIES_FILE_LOCATION = "wechatpay.properties";

    private static String key;
    private static String appId;
    private static String appIdInOfficialAccounts;
    private static String mchId;
    private static String signType;
    private static String notifyUrl;
    private static String tradeTypeApp;
    private static String tradeTypeJsapi;

    static{
        Properties properties = new Properties();
        try(Reader in = new InputStreamReader(WechatPayUtil.class.getClassLoader().getResourceAsStream(PROPERTIES_FILE_LOCATION), "UTF-8")) {
            properties.load(in);
        } catch (FileNotFoundException e) {
            LOGGER.error("初始化微信配置失败", e);
        } catch (IOException e) {
            LOGGER.error("初始化微信配置失败", e);
        }
        key = properties.getProperty("pay.wechat.key");
        appId = properties.getProperty("pay.wechat.appId");
        appIdInOfficialAccounts = properties.getProperty("pay.wechat.appIdInOfficialAccounts");
        mchId = properties.getProperty("pay.wechat.mchId");
        signType = properties.getProperty("pay.wechat.signType");
        notifyUrl = properties.getProperty("pay.wechat.notifyUrl");
        tradeTypeApp = properties.getProperty("pay.wechat.tradeTypeApp");
        tradeTypeJsapi = properties.getProperty("pay.wechat.tradeTypeJsapi");
    }

    /**
     * APP支付
     * @param body APP名字-实际商品名称
     * @param outTradeNo 订单号
     * @param totalFee 订单总金额，单位为分
     * @param spbillCreateIp 用户端实际ip
     * @return
     */
    public static WechatOrderResult appOrder(String body, String outTradeNo, int totalFee, String spbillCreateIp){
        //TODO
        totalFee=1;
        WeChat wc = new WeChat();
        wc.setAppid(appId);
        wc.setMchId(mchId);
        wc.setNonceStr(gen32NonceStr());
        /*wc.setSignType(signType);*/
        wc.setBody(body);
        wc.setOutTradeNo(outTradeNo);
        wc.setTotalFee(totalFee);
        wc.setSpbillCreateIp(spbillCreateIp);
        wc.setTimeStart(genTimeStart());
        wc.setTimeExpire(genTimeExpire());
        wc.setNotifyUrl(notifyUrl);
        wc.setTradeType(tradeTypeApp);
        genSign(wc);
        String requestXml = entityToXml(wc);
        String response =  request(requestXml);
        return xmlToEntity(response);
    }

    /**
     * 生成32位随机数，使用uuid
     * @return
     */
    private static String gen32NonceStr(){
        UUID uuid = UUID.randomUUID();
        return uuid.toString().replaceAll("-", "");
    }

    /**
     * 订单开始时间（当前时间）
     * @return
     */
    private static String genTimeStart(){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        Date date = new Date();
        String now = sdf.format(date);
        return now;
    }
    /**
     * 订单结束时间（当前时间20分钟以后）
     * @return
     */
    private static String genTimeExpire(){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        Date date = new Date(System.currentTimeMillis()+1000*60*10);
        String twentyMinuteLater = sdf.format(date);
        return twentyMinuteLater;
    }

    private static void genSign(WeChat wechatload){
        StringBuffer sign = new StringBuffer();
        Field[] fields = wechatload.getClass().getDeclaredFields();
        fieldSortedByFieldName(fields);
        for (int i = 0; i < fields.length; i++) {
            String fieldName = fields[i].getName();
            Method getMethod;
            try {
                getMethod = wechatload.getClass().getMethod(getGetMethodNameByFieldName(fieldName));
                Object fieldValue = getMethod.invoke(wechatload);
                if (fieldValue!=null&&!fieldValue.equals("")) {
                    sign.append(changeCamelName(fieldName));
                    sign.append("=");
                    sign.append(fieldValue);
                    sign.append("&");
                }
            } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                LOGGER.error("微信支付生成sign异常", e);
            }

        }
        sign.append("key=");
        sign.append(key);
        LOGGER.debug("需签名数据为"+sign);
        wechatload.setSign(MD5Util.MD5EncodeUtf8(sign.toString()).toUpperCase());
    }

    /**
     * 排序
     * @param fields
     */
    private static void fieldSortedByFieldName(Field[] fields){
        Arrays.sort(fields,new Comparator<Field>() {

            @Override
            public int compare(Field field1, Field field2) {
                return field1.getName().compareTo(field2.getName());
            }
        });
    }

    /**
     *
     * @param fieldName
     * @return
     */
    private static String getGetMethodNameByFieldName(String fieldName){
        return "get" +fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
    }

    /**
     * 驼峰转换成_
     * @param str
     * @return
     */
    private static String changeCamelName(String str){
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (c>='A'&&c<='Z') {
                c = (char) (c+32);
                sb.append("_");
            }
            sb.append(c);
        }
        return sb.toString();
    }

    private static String entityToXml(Object entity){
        try {

            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory
                    .newInstance();
            DocumentBuilder documentBuilder = documentBuilderFactory
                    .newDocumentBuilder();
            org.w3c.dom.Document document = documentBuilder.newDocument();
            org.w3c.dom.Element root = document.createElement("xml");
            document.appendChild(root);
            Field[] fields = entity.getClass().getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                String fieldName = fields[i].getName();
                Method getMethod;
                try {
                    getMethod = entity.getClass().getMethod(getGetMethodNameByFieldName(fieldName));
                    Object fieldValue = getMethod.invoke(entity);
                    if (fieldValue==null) {
                        continue;
                    }
                    String stringValue = fieldValue.toString();
                    stringValue = stringValue.trim();
                    org.w3c.dom.Element filed = document.createElement(changeCamelName(fieldName));
                    filed.appendChild(document.createTextNode(stringValue));
                    root.appendChild(filed);
                } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                    LOGGER.error("微信支付生成xml异常", e);
                }
            }
            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer transformer = tf.newTransformer();
            DOMSource source = new DOMSource(document);
            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            StringWriter writer = new StringWriter();
            StreamResult result = new StreamResult(writer);
            transformer.transform(source, result);
            String output = writer.getBuffer().toString();
            writer.close();
            return output;
        } catch (Exception e) {
            LOGGER.error("xml转换异常", e);
            return "";
        }
    }

    private static String request(String requestXml){
        BasicHttpClientConnectionManager connManager = new BasicHttpClientConnectionManager(
                RegistryBuilder.<ConnectionSocketFactory>create()
                        .register("http", PlainConnectionSocketFactory.getSocketFactory())
                        .register("https", SSLConnectionSocketFactory.getSocketFactory())
                        .build(),
                null,
                null,
                null
        );
        HttpClient httpClient = HttpClientBuilder.create()
                .setConnectionManager(connManager)
                .build();
        String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        HttpPost httpPost = new HttpPost(url);

        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(50000).setConnectTimeout(50000).build();
        httpPost.setConfig(requestConfig);

        StringEntity postEntity = new StringEntity(requestXml, "UTF-8");
        httpPost.addHeader("Content-Type", "text/xml");
        httpPost.setEntity(postEntity);

        HttpResponse httpResponse;
        try {
            httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();
            return EntityUtils.toString(httpEntity, "UTF-8");
        } catch (IOException e) {
            LOGGER.error("调用统一下单接口失败", e);
            return "";
        }

    }

    private static WechatOrderResult xmlToEntity(String strXML) {
        try {
            WechatOrderResult result =  new WechatOrderResult();
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
            InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
            org.w3c.dom.Document doc = documentBuilder.parse(stream);
            doc.getDocumentElement().normalize();
            NodeList nodeList = doc.getDocumentElement().getChildNodes();
            for (int idx = 0; idx < nodeList.getLength(); ++idx) {
                Node node = nodeList.item(idx);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    org.w3c.dom.Element element = (org.w3c.dom.Element) node;
                    String fieldName = camelChange(element.getNodeName());
                    Method method = result.getClass().getDeclaredMethod(getSetMethodNameByFieldName(fieldName), String.class);
                    method.invoke(result, element.getTextContent());
                }
            }
            stream.close();
            return result;
        } catch (Exception ex) {
            LOGGER.error("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
            return null;
        }

    }

    /**
     * 转换成驼峰命名
     * @param str
     * @return
     */
    private static String camelChange(String str){
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (c=='_') {
                c = str.charAt(++i);
                c = (char) (c-32);
            }
            sb.append(c);
        }
        return sb.toString();
    }

    /**
     *
     * @param fieldName
     * @return
     */
    private static String getSetMethodNameByFieldName(String fieldName){
        return "set" +fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
    }
    public static void appSign(Map<String, String> apiResult){
        String appid = appId;
        apiResult.put("appid", appid);
        String partnerid = mchId;
        apiResult.put("partnerid",partnerid);
        String prepayid = apiResult.get("prepayid");
        String package1 = "Sign=WXPay";
        apiResult.put("package", package1);
        String noncestr = UUIDUtil.get32UUID();
        apiResult.put("noncestr", noncestr);
        String timestamp = String.valueOf(System.currentTimeMillis()/1000l);
        apiResult.put("timestamp", timestamp);
        StringBuffer sb = new StringBuffer();
        sb.append("appid=");
        sb.append(appid);
        sb.append("&noncestr=");
        sb.append(noncestr);
        sb.append("&package=");
        sb.append(package1);
        sb.append("&partnerid=");
        sb.append(partnerid);
        sb.append("&prepayid=");
        sb.append(prepayid);
        sb.append("&timestamp=");
        sb.append(timestamp);
        String sign = MD5Util.MD5EncodeUtf8(sb.toString()).toUpperCase();
        apiResult.put("sign", sign);
    }

    public static boolean isSuccess(WechatOrderResult result) {
        if (result.getReturnCode()!=null&&result.getReturnCode().equals("SUCCESS")&&result.getResultCode()!=null&&result.getResultCode().equals("SUCCESS")) {
            return true;
        }
        return false;
    }
}
